package owlapps2012.owlToSql.events;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLObjectProperty;

import owlapps2012.owlToSql.base.BracketToAdd;
import owlapps2012.owlToSql.base.DomainClassMapping;
import owlapps2012.owlToSql.base.DefaultAppConfig;
import owlapps2012.owlToSql.base.DomainPropertyMapping;
import owlapps2012.owlToSql.base.OWLMinRestriction;
import owlapps2012.owlToSql.base.OwlToSqlTransformationException;
import owlapps2012.owlToSql.base.TextProcessingUtils;
import owlapps2012.owlToSql.base.Transformer;
import uk.ac.manchester.cs.owl.owlapi.mansyntaxrenderer.ManchesterOWLSyntaxOWLObjectRendererImpl;

/**
* Author: Piotr Nowara<br>
* piotrnowara@gmail.com<br>
* code made available under Mozilla Public License (http://www.mozilla.org/MPL/MPL-1.1.html)<br>
* copyright 2012, Piotr Nowara<br>
*/
public class EventsTransformer extends Transformer
{
	private static final String VERSION = "20121017_1";

	public EventsTransformer(DefaultAppConfig c) {
		super(c);
	}

	protected String transformTemporalRelation(OWLObjectProperty temporalObjProp) throws Exception
	{
		return transformTemporalRelation(temporalObjProp.getIRI().getFragment());
	}
	
	/*
	 * {Q1} has to be mapped to a table which a given EXISTS clause is defined
	 * {Q2} has to be mapped to a table from the EXISTS clause
	 */
	protected String transformTemporalRelation(String name) throws OwlToSqlTransformationException
	{
		String result;
		String additionalCondition = null;
		if(!(name.endsWith("after") || name.endsWith("before") || name.endsWith("simultaneously")))
		{
			int i = name.lastIndexOf('_'); 
			String paticipantType = name.substring(i+1);//the last word
			name = name.substring(0,i);
			if(conf.getAdditionalCondition(paticipantType) != null)
				additionalCondition = "{Q1}.".concat(conf.getAdditionalCondition(paticipantType)).concat(" = {Q2}.").concat(conf.getAdditionalCondition(paticipantType)).concat(" AND ");
			else
				throw new OwlToSqlTransformationException("Unsupported type of participant: "+paticipantType);

		}
		else if(conf.getAdditionalCondition("default") != null)
			additionalCondition = "{Q1}.".concat(conf.getAdditionalCondition("default")).concat(" = {Q2}.").concat(conf.getAdditionalCondition("default")).concat(" AND ");
		
		if(name.equals("occurs_simultaneously"))
			result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"{Q1}.{DATE_FIELD} = {Q2}.{DATE_FIELD} AND {Q1}."+conf.getIdColumn()+" <> {Q2}."+conf.getIdColumn()+" ";
		else if(name.matches("occurs_[_0-9a-zA-Z]{0,}before"))//żeby przechodziły szersze nazwy (np. "_for_the_same_patient", to trzeba zamienić na: name.matches("occurs_[_0-9a-zA-Z]{0,}before[_a-zA-Z]{0,}
		{
			if(name.equals("occurs_before"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"{Q1}.{DATE_FIELD} < {Q2}.{DATE_FIELD} ";
			else if(!name.contains("_day"))
				throw new OwlToSqlTransformationException("Unsupported time unit of a temporal object property: "+name);
			else if(name.contains("between"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"("+conf.getDBSpecificDateDifference("{Q2}.{DATE_FIELD} - {Q1}.{DATE_FIELD}")+" BETWEEN "+TextProcessingUtils.parseNumberOfDaysBetween(name)+") ";
			else if(name.startsWith("occurs_max"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"("+conf.getDBSpecificDateDifference("{Q2}.{DATE_FIELD} - {Q1}.{DATE_FIELD}")+" BETWEEN 1 AND "+TextProcessingUtils.parseNumberOfDays(name)+") ";
			else if(name.startsWith("occurs_min"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+conf.getDBSpecificDateDifference("{Q2}.{DATE_FIELD} - {Q1}.{DATE_FIELD}")+" >= "+TextProcessingUtils.parseNumberOfDays(name)+" ";
			else if(name.substring(0,8).matches("occurs_[0-9]"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+conf.getDBSpecificDateDifference("{Q2}.{DATE_FIELD} - {Q1}.{DATE_FIELD}")+" = "+TextProcessingUtils.parseNumberOfDays(name)+" ";
			else 
				throw new OwlToSqlTransformationException("Unsupported temporal object property: "+name);
		}
		else if(name.matches("occurs_[_0-9a-zA-Z]{0,}after"))
		{
			if(name.equals("occurs_after"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"{Q1}.{DATE_FIELD} > {Q2}.{DATE_FIELD} ";
			else if(!name.contains("_day"))
				throw new OwlToSqlTransformationException("Unsupported time unit of a temporal object property: "+name);
			else if(name.contains("between"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"("+conf.getDBSpecificDateDifference("{Q1}.{DATE_FIELD} - {Q2}.{DATE_FIELD}")+" BETWEEN "+TextProcessingUtils.parseNumberOfDaysBetween(name)+") ";
			else if(name.startsWith("occurs_max"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+"("+conf.getDBSpecificDateDifference("{Q1}.{DATE_FIELD} - {Q2}.{DATE_FIELD}")+" BETWEEN 1 AND "+TextProcessingUtils.parseNumberOfDays(name)+") ";
			else if(name.startsWith("occurs_min"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+conf.getDBSpecificDateDifference("{Q1}.{DATE_FIELD} - {Q2}.{DATE_FIELD}")+" >= "+TextProcessingUtils.parseNumberOfDays(name)+" ";
			else if(name.substring(0,8).matches("occurs_[0-9]"))
				result = "SELECT 1 FROM {TABLE_NAME} {Q2} WHERE "+(additionalCondition != null ? additionalCondition : "")+conf.getDBSpecificDateDifference("{Q1}.{DATE_FIELD} - {Q2}.{DATE_FIELD}")+" = "+TextProcessingUtils.parseNumberOfDays(name)+" ";
			else 
				throw new OwlToSqlTransformationException("Unsupported temporal object property: "+name);
		}
		else
			throw new OwlToSqlTransformationException("Unsupported temporal object property: "+name);
		
		
		return result.replaceAll("\\{DATE_FIELD\\}", conf.getDateColumn());
		
	}
	
	protected String transformDomainRelation(String propertyName, String propertyValue) throws OwlToSqlTransformationException
	{
		DomainPropertyMapping mapping = this.conf.getDomainPropertyMapping(propertyName);
		if(mapping != null)
			return mapping.getWhereExpression().replace("{VALUE}", propertyValue).concat(" ");
		else
			throw new OwlToSqlTransformationException("No configuration has been provided for the domain property: "+propertyName);
	}
	
	protected String transformDomainClass(String className) throws OwlToSqlTransformationException
	{
		DomainClassMapping mapping = this.conf.getDomainClassMapping(className);
		if(mapping != null)
			return mapping.getTableName();
		else
			throw new OwlToSqlTransformationException("No configuration has been provided for class: "+className);
	}

	public static boolean checkIfTemporalRelation(OWLObjectProperty objProp)
	{
		return checkIfTemporalRelation(objProp.getIRI().getFragment());
	}

	public static boolean checkIfTemporalRelation(String objPropName)
	{
//		return (objPropName.startsWith("follows") || objPropName.startsWith("preceeds") || objPropName.equals("occurs_simultaneously"));
		return (objPropName.startsWith("occurs_") || objPropName.startsWith("follows") || objPropName.startsWith("preceeds"));
	}
	
//	public static boolean checkIfEvent(String objPropName)
//	{
//		return (objPropName.startsWith("follows") || objPropName.startsWith("preceeds") || objPropName.equals("occurs_simultaneously"));
//	}

	public String transformOWLClassExpression(OWLClassExpression category) throws OwlToSqlTransformationException 
	{
		ManchesterOWLSyntaxOWLObjectRendererImpl renderer = new ManchesterOWLSyntaxOWLObjectRendererImpl();
		String expressionToTransform = renderer.render(category);
		return transformOWLClassExpression(expressionToTransform);
	}
	
	/*
	 * ASSUMPTIONS:
	 * - every temporal property casues adding a new bracket and the fact of closing that bracket sets the flag for updating names of tables 
	 * - this code uses simply method for updating names: the names derived from domain properties are substituted for every occurence of the '\{TABLE_NAME\} Q[0-9]{1,}' string Q number is the greatest available - after performing the substitution the replacement flag is swithced to FALSE and can be switched TRUE only after changing the statement level (detecting the fact of closing of an additional bracket)
	 * - no double brackets left after removing unneccessary ones in preprocessing phase (for now they are completely rejected, could be probably safely used outside min X expressions)
	 * - class names are only allowed at the beginning of a section
	 */
	public String transformOWLClassExpression(String expressionToTransform) throws OwlToSqlTransformationException 
	{
		System.out.println(" *** EventsTransformer ".concat(VERSION));
		System.out.println(" *** Starting transformation of following OWL expression:\n".concat(expressionToTransform));
		doValidation(expressionToTransform);
		expressionToTransform = doPreprocesing(expressionToTransform);
		Matcher matcher = TextProcessingUtils.pattern.matcher(expressionToTransform);
		String transformedExpression = "SELECT * FROM {TABLE_NAME} S1L1 WHERE";
		
		boolean checkNames = true;
//		boolean isDirectNestedTemporalProperty = false;
		int hitCount = 0;
		int countSectionsFromEXISTS = -1;
		int level = 1;
		int latestTableIndex = 1;//used for naming qualifiers, temporal property increments its value
		String latestTableName = "S1L1";
		ArrayList<BracketToAdd> listOfBracketsToBeAdded = new ArrayList<BracketToAdd>();//indexes of closing brackets to be added 
		ArrayList<OWLMinRestriction> listOfMinRestrictions = new ArrayList<OWLMinRestriction>();//for now we support SOME & MIN restrictions 
		ArrayList<String> listOfTableNames = new ArrayList<String>();
		listOfTableNames.add(latestTableName);
		while(matcher.find())
		{
			boolean bracketsAdded = false;
			boolean isCardinalitySectionJustTransformed = false;
			hitCount++;
			String match = matcher.group().trim();
			String className = matcher.group(4) != null ? matcher.group(4).split(" ")[0] : null;
			String logicalOperator = TextProcessingUtils.determineLogicalOperator(matcher.group(1), hitCount).toUpperCase();
			String propertyName = matcher.group(5);
			String propertyValue = null;
			if(matcher.group(7) != null)
				propertyValue = matcher.group(7);
			else if(matcher.group(12) != null)
				propertyValue = matcher.group(12).startsWith("\"") ? matcher.group(13) : matcher.group(12);
				
			String startingBrackets = matcher.group(3);
			String endingBrackets = matcher.group(16);
			String restrictionType = matcher.group(6).startsWith("value") ? "value" : matcher.group(6);
			String not;
			if(matcher.group(2) != null)
			{
				not = matcher.group(2).trim().toUpperCase();//				WARNING! omitting brackets before NOT caused bad SQL when NOT occurs directly after class name: Drug_issuance and ((not	(occurs_max_10_days_before min 2 (Drug_issuance and issued_drug_ATC some B01AZ))) or occurs_max_10_days_after some (Drug_issuance and issued_drug_ATC min 1 B01AZ)) and ((not	(occurs_max_10_days_before min 2 (Drug_issuance and issued_drug_ATC some B01AZ))) or occurs_max_10_days_after some (Drug_issuance and issued_drug_ATC min 1 B01AZ)) and (issued_drug_ATC some A01AZ or issued_drug_ATC some B01A) and issued_drug_ATC some A01AA
//				while(not.startsWith("("))
//					not = not.substring(1).trim();
//				if(!not.startsWith("NOT"))//just in case...
//					throw new OwlToSqlTransformationException("Expected 'not...' clause instead of: "+not);
			}
			else
				not = "";
			String comparisonSign = matcher.group(9);
			String comparisonValue = matcher.group(10);
			System.out.println(match + ";" + propertyName + ";" + restrictionType + ";" + propertyValue + ";" + endingBrackets);
			String lonelyClass = null;
			
			if(startingBrackets.length() > 3)
				throw new OwlToSqlTransformationException("Too many brackets!");
			if(comparisonSign != null && !propertyValue.startsWith("int"))
				throw new OwlToSqlTransformationException("Unsupported datatype: "+propertyValue);
			
			if(hitCount == 1)//w niektórych przypadkach nie musi być podanych własności dla zdarzenia które chcemy otrzymać, np: Drug_issuance and occurs_max_30_days_before min 1 (Medical_procedure and occurs_max_30_days_before min 1 (Diagnosis_indication and indicated_ICD10_code value "A10") and medical_procedure_ICD9_code value "22.01")
			{
				if(className == null)//the regex will not match the class name if a "not clasue" comes directly after - I don't want to complicate the pattern to detect something that can be easily done by adding 1 line of code
					className = expressionToTransform.split(" ")[0];
				transformedExpression = transformedExpression.replaceFirst("\\{TABLE_NAME\\}", this.transformDomainClass(className));
			}
			
			if(checkIfTemporalRelation(propertyName))
			{
//				if(logicalOperator.equals("or"))
//					throw new Exception("OR expression not allowed for temporal properties!");
//				if(countSectionsFromEXISTS == 0)//NIEUŻYWANE
//					isDirectNestedTemporalProperty = true;
//				else
//					isDirectNestedTemporalProperty = false;
				checkNames = true;
				countSectionsFromEXISTS = 0;
				int indexOfNextBracket = expressionToTransform.indexOf("(", matcher.end());
				if(indexOfNextBracket < 1)
					throw new OwlToSqlTransformationException("Too less brackets!");
				int indexOfTheClosingBracket = TextProcessingUtils.determineBracketRange(expressionToTransform, indexOfNextBracket);
				
				if(restrictionType.toLowerCase().startsWith("min "))
				{
					int cardinality = Integer.parseInt(restrictionType.substring(4));
					if(cardinality > conf.getMaxCardinality())
						throw new OwlToSqlTransformationException("Maximal cardinality limit exceeded!");
					if(!TextProcessingUtils.checkIfNested(listOfMinRestrictions))
						listOfMinRestrictions.add(new OWLMinRestriction(expressionToTransform, indexOfNextBracket, indexOfTheClosingBracket, cardinality, transformedExpression.length()));
					else
						throw new OwlToSqlTransformationException("Nested cardinality expressions are not supported!");
					if(hitCount > 1 && !startingBrackets.equals(""))
					{
						listOfBracketsToBeAdded.add(new BracketToAdd(transformedExpression.length() + 1, TextProcessingUtils.determineBracketRange(expressionToTransform, matcher.start()+matcher.group().indexOf('(')), hitCount, false));
					}
				}
				
				//always adding a bracket when transforming into an EXISTS clause
				if(indexOfNextBracket > -1)
					listOfBracketsToBeAdded.add(new BracketToAdd(transformedExpression.length() + 1, indexOfTheClosingBracket, hitCount, true));
				else
					throw new OwlToSqlTransformationException("Bad syntax! Temporal property without a bracket!");
				
				if(hitCount == 1 && !startingBrackets.equals(""))//need this to avoid inapropriate nesting - Protege does not allow constructs like "... and follows(...)"
					transformedExpression = transformedExpression.concat(" ").concat(startingBrackets).concat("1=1 AND ").concat(not).concat("EXISTS (").concat(this.transformTemporalRelation(propertyName));
				else if(hitCount > 1 && !startingBrackets.equals(""))
					transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(not).concat(startingBrackets).concat("EXISTS (").concat(this.transformTemporalRelation(propertyName));
//					transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" ( ").concat(not).concat("EXISTS ").concat(startingBrackets).concat(this.transformTemporalRelation(propertyName));
				else
					transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" ").concat(not).concat("EXISTS (").concat(this.transformTemporalRelation(propertyName));
				
				transformedExpression = transformedExpression.replace("{Q1}", TextProcessingUtils.determineLatestTableOfSpecifiedLevel(listOfTableNames, level));
				level++;
				latestTableIndex++;
				latestTableName = "S"+latestTableIndex+"L"+level;
				listOfTableNames.add(latestTableName);
				transformedExpression = transformedExpression.replace("{Q2}", latestTableName);
				
				//Support for lonely classes like: Drug_issuance i  (issued_drug_ATC some B01AC) i (occurs_min_1_day_before some (Drug_issuance))
				String s = expressionToTransform.substring(matcher.end()).trim().split(" ")[0];
				if(s.matches("\\([^\\s]{1,}\\)"))
				{
					lonelyClass = s.substring(1, s.length()-1);
					if(conf.getDomainClassMapping(lonelyClass) != null)
					{
						if(restrictionType.toLowerCase().startsWith("min "))
							transformedExpression = transformedExpression.replaceFirst("\\{TABLE_NAME\\} "+latestTableName, conf.getDomainClassMapping(lonelyClass).getTableName().concat(" ").concat(latestTableName));
						else
							transformedExpression = transformedExpression.replaceFirst("\\{TABLE_NAME\\} "+latestTableName, conf.getDomainClassMapping(lonelyClass).getTableName().concat(" ").concat(latestTableName)).concat(")");
//						checkNames = false;
						level--;
					}
					else
						throw new OwlToSqlTransformationException("No mapping for class name: ".concat(lonelyClass));
				}
					
				
			}
			else
			{
				countSectionsFromEXISTS++;
				if(restrictionType.toLowerCase().startsWith("min "))
					throw new OwlToSqlTransformationException("Cardinality restriction are allowed only for temporal properties.");
					
				if(not.equals(""))
				{
					if(comparisonValue == null)
						transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" ").concat(startingBrackets).concat(this.transformDomainRelation(propertyName, propertyValue));
					else
						transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" ").concat(startingBrackets).concat(this.transformDomainRelation(propertyName, comparisonValue).replace("=", comparisonSign).replace("'", ""));
				}
				else 
					transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" ").concat(not).concat(startingBrackets).concat(this.transformDomainRelation(propertyName, propertyValue));//This line replaced the on below: uses NOT instead of messing with comparison signs whic is cleaner (will allow safe use of LIKE statements)
//				transformedExpression = transformedExpression.concat(" ").concat(logicalOperator).concat(" (").concat(startingBrackets).concat(this.transformDomainRelation(propertyName, propertyValue).replace("=", "<>"));

				//update name in all occurences of the current table (in all EXISTS clauses where the current table occurs)
				if(checkNames)
				{
					int matchedTableIndex = 0;
					String qualifier = null;
					for(int i=latestTableIndex;i>0;i--)
					{
						Matcher matcherForTableNames = Pattern.compile("\\{TABLE_NAME\\} (S["+i+"]L["+level+"]{1,})").matcher(transformedExpression);
						if(matcherForTableNames.find())
						{
							matchedTableIndex = i;
							qualifier = matcherForTableNames.group(1);
							break;
						}
					}
					if(matchedTableIndex > 0)
					{
						transformedExpression = transformedExpression.replaceAll("\\{TABLE_NAME\\} "+qualifier, this.conf.getDomainPropertyMapping(propertyName).getTableName().concat(" ").concat(qualifier));
						checkNames = false;
					}
					else//there can be more than 1 occurences of a adomain property separated by a temporal property - for example: (follows some (issued_drug_ATC_code value \"AA7AA\"^^string and issued_drug_ATC_code value \"2B7AA\"^^string and follows some (issued_drug_ATC_code value \"UUUUU\"^^string))) and issued_drug_ATC_code value \"BB7AA\"^^string and not (follows some (issued_drug_ATC_code value \"AA7AA\"^^string and issued_drug_ATC_code value \"2B7AA\"^^string and follows some (issued_drug_ATC_code value \"UUUUU\"^^string))) and issued_drug_ATC_code value \"BB7AA\"^^string
					{
						qualifier = TextProcessingUtils.determineLatestTableOfSpecifiedLevel(listOfTableNames, level);
						checkNames = false;
					}
					//update qualifiers
					transformedExpression = transformedExpression.replace("{Q}", qualifier);
				}
				else 
					transformedExpression = transformedExpression.replace("{Q}", TextProcessingUtils.determineLatestTableOfSpecifiedLevel(listOfTableNames, level));
				
			}
					
			//MIN clauses
			if(listOfMinRestrictions.size() > 0)
			{
				
				for(int i=0;i<listOfMinRestrictions.size();i++)
				{
					
					OWLMinRestriction mR = listOfMinRestrictions.get(i);
					if(!mR.isTransformed())
					{
						isCardinalitySectionJustTransformed = true;
						//update SQL
						mR.setSqlFragmentToTransform(transformedExpression.substring(mR.getStartPoint()));
						
						//transform
						if(mR.getIndexOfClosingBracket() <= matcher.end() || lonelyClass != null)
						{
							String sqlF = mR.getSqlFragmentToTransform();
							if(sqlF.trim().startsWith("OR"))//trzeba zadbać o to aby przepisana relacja czasowa miała AND a nie inne sójniku logiczne z wyższego poziomu!
								sqlF = sqlF.replaceFirst("OR", "AND");
							if(sqlF.trim().startsWith("AND NOT"))
								sqlF = sqlF.replaceFirst("AND NOT", "AND");
							
							if(sqlF.toUpperCase().trim().startsWith("NOT"))
								sqlF = sqlF.substring(4);
							if(!(sqlF.toUpperCase().trim().startsWith("OR") || sqlF.toUpperCase().trim().startsWith("AND")))
								sqlF = "AND ".concat(sqlF); 
							ArrayList<String> listOfOpeningQualifiers = new ArrayList<String>();
							String currentOpeningQualifier = null;
							for(int j=0;j<(mR.getCardinality()-1);j++)
							{
								Matcher levelMatcher = TextProcessingUtils.cRestrPattern.matcher(sqlF);
								int c = 0;
								while(levelMatcher.find())
								{
									c++;
									String q = levelMatcher.group(1);
									String newQ = levelMatcher.group(2).concat(String.valueOf(Integer.parseInt(levelMatcher.group(3)) + 1));//always 1 because operating on curretly changed string (previously incremented qualifier)
									sqlF = sqlF.replaceAll(q, newQ);
									if(c == 1)
									{
										if(j == 0)
											listOfOpeningQualifiers.add(q);
										currentOpeningQualifier = newQ;
										if(!listOfOpeningQualifiers.contains(newQ))
											listOfOpeningQualifiers.add(newQ);
									}
								} 
								mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(sqlF));
								for(int k=0; k<listOfOpeningQualifiers.size()-1;k++)//ommiting the last one (current)
								{
									if(logicalOperator.equals("OR"))
									{
										if(k == 0)
											mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(")"));
										mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(" AND (").concat(listOfOpeningQualifiers.get(k).concat("."+conf.getIdColumn()+" <> ").concat(currentOpeningQualifier).concat("."+conf.getIdColumn()+")")));
									}
									else
										mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(" AND ").concat(listOfOpeningQualifiers.get(k).concat("."+conf.getIdColumn()+" <> ").concat(currentOpeningQualifier).concat("."+conf.getIdColumn()+" ")));
								}
							}
							
							String s = mR.getSqlFragmentToTransform().concat(mR.getTransformedSqlFragment());
							mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(TextProcessingUtils.returnMissingBrackets(s.substring(s.indexOf("SELECT")))));//zastąpiłem magię, która była poniżej!
//							if(s.trim().startsWith("(") || s.trim().startsWith("AND (") || s.trim().startsWith("OR ("))
//								mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(TextProcessingUtils.returnMissingBrackets(s.substring(s.indexOf('(')+1))));
//							else
//								mR.setTransformedSqlFragment(mR.getTransformedSqlFragment().concat(TextProcessingUtils.returnMissingBrackets(mR.getSqlFragmentToTransform().concat(mR.getTransformedSqlFragment()))));

							transformedExpression = transformedExpression.concat(mR.getTransformedSqlFragment());
//							bracketsAdded = true;//UWAGA! to powodowało problemy!
							mR.setTransformed(true);
						}
					}
				}
			}
			
			int bracketsLeft = TextProcessingUtils.returnMissingBrackets(transformedExpression).length();
			if(endingBrackets != null && endingBrackets.length()>0 && bracketsLeft > 0)
			{
				if(!bracketsAdded)
				{
					if(endingBrackets.length() > bracketsLeft)
						while(endingBrackets.length() > bracketsLeft)
							endingBrackets = endingBrackets.substring(1);
					transformedExpression = transformedExpression.concat(endingBrackets);
					bracketsAdded = true;
				}
//				else
//				{
//					if(endingBrackets.length() > 1)
//					{
//						int substrIndex = endingBrackets.length() - bracketsLeft;
//						if(substrIndex < 0)
//							substrIndex = 0;
//						transformedExpression = transformedExpression.concat(endingBrackets.substring(substrIndex));
//					}
//				}
			}

			//what brackets added by transformer need to be closed at this location?
			bracketsLeft = TextProcessingUtils.returnMissingBrackets(transformedExpression).length();
			if(listOfBracketsToBeAdded.size() > 0)
			{
				for(int i=0;i<listOfBracketsToBeAdded.size();i++)
					if(!listOfBracketsToBeAdded.get(i).isClosed() && listOfBracketsToBeAdded.get(i).getEndIndex() <= matcher.end())
					{
//						if(!bracketsAdded  && bracketsLeft > 0)
						if((!bracketsAdded || !isCardinalitySectionJustTransformed) && bracketsLeft > 0)
						{
							transformedExpression = transformedExpression.concat(")");
//							if(bracketsLeft > 1 && isDirectNestedTemporalProperty)//Drug_issuance and follows some ((Drug_issuance and follows... - direct nested temporal properties require additional bracket
//							{
//								transformedExpression = transformedExpression.concat(")");
//								isDirectNestedTemporalProperty = false;
//							}
						}
						listOfBracketsToBeAdded.get(i).setClosed(true);
						if(listOfBracketsToBeAdded.get(i).isChangesLevel())
						{
							if(!checkNames)
								checkNames = true;
							level--;
						}
						bracketsLeft = TextProcessingUtils.returnMissingBrackets(transformedExpression).length();
					}
			}
			
		}
		if(TextProcessingUtils.returnMissingBrackets(transformedExpression).length() > 0)
			transformedExpression = TextProcessingUtils.addMissingBracketsMinClause(transformedExpression);

		return doPostProcessing(transformedExpression);
	}

	private String doPostProcessing(String transformedExpression) throws OwlToSqlTransformationException 
	{
		Matcher m = Pattern.compile("((S[0-9]{1,}L)([0-9]{1,}))."+conf.getIdColumn()+" <> ((S[0-9]{1,}L)([0-9]{1,}))."+conf.getIdColumn()+" ").matcher(transformedExpression);
		while(m.find())
		{
			String firstQualifier = m.group(1);
			String secondQualifier = m.group(4);
			
			
			String t1 = TextProcessingUtils.getTableNameByQualifier(transformedExpression, firstQualifier);
			String t2 = TextProcessingUtils.getTableNameByQualifier(transformedExpression, secondQualifier);
			
			if(t1 != null && t2 != null)
				if(!(t1.equals(t2)))
				{
					transformedExpression = transformedExpression.replaceAll("AND ".concat(m.group()), "");
					System.out.println(m.group(1));
					System.out.println(m.group(4));
					System.out.println();
				}
			
		}

		if(transformedExpression.contains("{TABLE_NAME}"))
			throw new OwlToSqlTransformationException("Unsupported definition: the program could not match all table names.");

		return transformedExpression;
	}


	private void doValidation(String expressionToTransform) throws OwlToSqlTransformationException 
	{
		Matcher m = TextProcessingUtils.classNameWithOR.matcher(expressionToTransform);
		if(m.find())
			throw new OwlToSqlTransformationException("'OR' operator not allowed after a class name");
		m = TextProcessingUtils.classNameNotAtBeginning.matcher(expressionToTransform);
		if(m.find())
		{
			System.out.println(m.group());
			throw new OwlToSqlTransformationException("Class name is allowed only at the beginning of a section");
		}
		
	}

	private String doPreprocesing(String expressionToTransform) throws OwlToSqlTransformationException 
	{
		String sBackup = expressionToTransform;
		
		expressionToTransform = expressionToTransform.replaceAll("\t", " ");
		expressionToTransform = expressionToTransform.replaceAll("\n", " ");
		expressionToTransform = expressionToTransform.replaceAll("\r", " ");
		expressionToTransform = expressionToTransform.replaceAll(" {2,}", " ");
		
		Matcher m = TextProcessingUtils.owlSAFEPattern.matcher(expressionToTransform);//zabezpieczenie przeciw używaniu wyrażeń logicznych w ciągach znaków
		if(m.find())
			throw new OwlToSqlTransformationException("Logical keyword or bracket used in 'value' section.");
		
		m = TextProcessingUtils.owlMin1Pattern.matcher(expressionToTransform);//min 1 -> some
		if(m.find())
			expressionToTransform = m.replaceAll(" some ");
		
		//w przyszłości dodać warunek na wspieranie polskich wyrażeń
		m = TextProcessingUtils.plNIEPattern.matcher(expressionToTransform);//nie ( -> not (
		if(m.find())
			expressionToTransform = m.replaceAll(" not (");
		do
		{
			m = TextProcessingUtils.plNIEPattern2.matcher(expressionToTransform);//(nie ( -> not (
			if(m.find())
				expressionToTransform = m.replaceFirst(" ".concat(m.group(1)).concat("not ("));
			m = TextProcessingUtils.plNIEPattern2.matcher(expressionToTransform);//(nie ( -> not (
		}
		while(m.find());
		m = TextProcessingUtils.plIPattern.matcher(expressionToTransform);//i -> and
		if(m.find())
			expressionToTransform = m.replaceAll(" and ");
		m = TextProcessingUtils.plLUBPattern.matcher(expressionToTransform);//lub -> or
		if(m.find())
			expressionToTransform = m.replaceAll(" or ");
		m = TextProcessingUtils.plKONIECZNIE1Pattern.matcher(expressionToTransform);//koniecznie -> some
		if(m.find())
			expressionToTransform = m.replaceAll(" some ");
		
		expressionToTransform = removeUnneededBrackets(expressionToTransform);
		
		m = TextProcessingUtils.notPattern.matcher(expressionToTransform);
		while(m.find())
		{
			String entityAfter = expressionToTransform.substring(m.end()).split(" ")[0];
			if(checkIfTemporalRelation(entityAfter))
			{
				int nonRelativeIndexOf1Bracket = m.start() + m.group().indexOf('(');
				int endOfFirstBracket = TextProcessingUtils.determineBracketRange(expressionToTransform, nonRelativeIndexOf1Bracket);
				int secondVracketStart =  expressionToTransform.indexOf('(', nonRelativeIndexOf1Bracket+1);
				if(secondVracketStart > -1)
				{
					int endOfSecondBrackt = TextProcessingUtils.determineBracketRange(expressionToTransform, secondVracketStart);
					if(endOfFirstBracket - endOfSecondBrackt - 1 > 0)
						throw new OwlToSqlTransformationException("'not' clause can not span over more than one relation.");
				}
			}
			else
			{
				String contentOfNegatedBracket = expressionToTransform.substring(m.end(), m.end() + TextProcessingUtils.determineBracketRange(expressionToTransform.substring(m.end()-1), 0));
				if(contentOfNegatedBracket.contains(" and ") || contentOfNegatedBracket.contains(" i ") || contentOfNegatedBracket.contains(" or ") || contentOfNegatedBracket.contains(" lub "))
					throw new OwlToSqlTransformationException("'not' clause can not span over more than one relation.");
			}
		}

		
		
		//add another if needed...
		if(!expressionToTransform.equals(sBackup))
			System.out.println("Initial preprocessing:\n"+expressionToTransform);
		return expressionToTransform;
	}

	/*
	 * 1. jeśli w nawiasie jest OR lub AND, to zostaje 
	 * 2. jeśli bezpośrendo po nazwie relacj z wyrażeniem SOME lub MIN, to zostaje 
	 * 3. jeśli po NOT, to zostaje
	 * 4. w przeciwnym razie nawias jest usuwany (jeśli po otwarciu nawiasu jest następny nawias, to najpierw rozpatrujemy ten zagnieżdżony).
	 */
	public static String removeUnneededBrackets(String expression) throws OwlToSqlTransformationException 
	{
		boolean isChanged = false;
		do
		{
			Matcher m = TextProcessingUtils.oBracketPattern.matcher(expression);
			isChanged = false;
			while(m.find())
			{
				int beginIndex = m.start();
				if(expression.charAt(beginIndex + 1) == '(')
				{
					continue;
				}
				else
				{
					if(beginIndex >= 4)
					{
						if(expression.substring(beginIndex - 4, beginIndex).equalsIgnoreCase("not "))//3
							continue;
					}
					if(beginIndex >= 5)
					{
						if(expression.substring(beginIndex - 5, beginIndex).equalsIgnoreCase("some "))//2
							continue;
					}
					if(beginIndex >= 6)
					{
						boolean isMinExpr = false;
						int spaceCount = 0;
						for(int i=beginIndex-1;i>=0;i--)
						{
							if(expression.charAt(i) == ' ')
								spaceCount++;
							else if(expression.charAt(i) == ')')
							{
								isMinExpr = false;
								break;
							}
							
							if(spaceCount == 3)
							{
								if(expression.toLowerCase().startsWith("min", i+1))
									isMinExpr = true;
								break;
							}
						}
						if(isMinExpr)//2
							continue;
					}
						
					int endIndex = TextProcessingUtils.determineBracketRange(expression, beginIndex);
					if(endIndex <= expression.length() - 1)
					{
						String frgm = expression.substring(beginIndex+1);
						String relation = frgm.substring(0, expression.substring(beginIndex+1).indexOf(" "));
						int indexOfNextBracket = frgm.indexOf(")");
				    	if(frgm.substring(0, indexOfNextBracket).toLowerCase().contains(" or "))//1
							continue;
				    	else if(!EventsTransformer.checkIfTemporalRelation(relation) && frgm.substring(0, indexOfNextBracket).toLowerCase().contains(" and "))//1
				    		continue;
				    	else if(expression.substring(beginIndex+1, endIndex).contains(") or follows") || expression.substring(beginIndex+1, endIndex).contains(") or preceeds")
				    			|| expression.substring(beginIndex+1, endIndex).contains(") or occurs") || expression.substring(beginIndex+1, endIndex).contains("or not (occurs") || expression.substring(beginIndex+1, endIndex).contains("or (occurs"))
				    		continue;
				    	
//						int indexOfClosingBracket = expression.substring(beginIndex + 1).indexOf(")");
//						if(indexOfClosingBracket > -1 && expression.substring(beginIndex + 1, (beginIndex + 1) + indexOfClosingBracket).toLowerCase().contains(" or "))//1
//				    		continue;
//				    	else if(indexOfClosingBracket > -1 && expression.substring(beginIndex + 1, (beginIndex + 1) + indexOfClosingBracket).toLowerCase().contains(" and "))//1
//				    		continue;

//				    	else if(indexOfNextBracket > -1 && expression.substring(beginIndex - 3, beginIndex).toLowerCase().equals("or "))//1a
//				    		continue;
//				    	else if(expression.substring(endIndex + 1, endIndex + 4).equalsIgnoreCase(" or"))
//				    		continue;
					}

					expression = expression.substring(0, beginIndex).concat(expression.substring(beginIndex + 1, endIndex)).concat(expression.substring(endIndex + 1));
					isChanged = true;
					break;
					
				}
			}
		}
		while(isChanged);
		return expression;
	}
}
